#ifndef PLAYER_H
#define PLAYER_H

#include "../sprite_handler.h"
#include "../input.h"
#include "../scrolling.h"
#include "laser.h"
#include "../scrolling.h"
#include "../velocity.h"
#include "../game_states.h"
#include "enemy.h"
#include "../sound.h"

// 0 <= total_velocity <= 26


////////////////////////////////////////////////////////////
// other defines
////////////////////////////////////////////////////////////
#define WAIT_BEFORE_DIRECTION_CHANGE 70

#define CENTER_X 112    //(SCREEN_WIDTH >> 1) - 16
#define CENTER_Y 72     // (SCREEN_HEIGHT >> 1) - 16

#define CAMERA_BORDER_BUFFER_WIDTH 	120
#define CAMERA_BORDER_BUFFER_HEIGHT 80

#define INITIAL_VELOCITY_FOR_TWO_PIXEL_AT_A_TIME_MOVEMENT 10

#define WAIT_BEFORE_ONE_PIXEL_ACCELERATION 100
#define WAIT_BEFORE_TWO_PIXEL_ACCELERATION 500

#define WAIT_BEFORE_STANDARD_DECELERATION 110
#define WAIT_BEFORE_BRAKED_DECELERATION 15

#define TWO_PIXEL_VELOCITY_BUFFER 4


////////////////////////////////////////////////////////////
// global variables
////////////////////////////////////////////////////////////

// Player Data
typedef struct STRUCT_PLAYER_DATA {	
	int map_position_x, map_position_y;
	
	int wait_x, wait_y;
	int dir_x, dir_y;
	
	int millisecondsSinceLastDirectionChange;
    int millisecondsSinceLastLaser;
    
    int milliseconds_since_last_move_x;
    int milliseconds_since_last_move_y;
	
	int laser_interval;
	
	int acceleration;
	int deceleration;
	
	int milliseconds_since_last_velocity_increase;
	int milliseconds_since_last_velocity_decrease;
	
	int current_velocity;
	int max_velocity;
	int total_velocity;
	
	BOOL is_moving_two_pixels_at_a_time;
	BOOL warp_drive_is_engaged;
	
	int level_one_enemies_killed, level_two_enemies_killed, level_three_enemies_killed;
	int total_enemies_killed;
	
	int level;
	int total_velocity_cap;
	
	BOOL has_earned_warp_drive;
	
} PlayerData;

int g_player_map_pos_x, g_player_map_pos_y;


void addNextEnemy();	// forward declaration
void replaceWithExplosion( SpriteHandler * sprite );
void SetDeathState();
void SetVictoryState();

////////////////////////////////////////////////////////////
// function prototypes
////////////////////////////////////////////////////////////
void playerUpdateFunction( SpriteHandler * );
void UpdatePlayerShipPosition(SpriteHandler *);
void UpdateFireLaser(SpriteHandler *);

void SetDirX(SpriteHandler *);
void SetDirY(SpriteHandler *);

void RotatePlayerCounterClockwise(SpriteHandler *);
void RotatePlayerClockwise(SpriteHandler *);

void IncreasePlayerVelocity(SpriteHandler *);
void DecreasePlayerVelocity(SpriteHandler *);

int GetPlayerMapPosX(void);
int GetPlayerMapPosY(void);

void ManageWarpDrive(SpriteHandler *);
void ManagePlayerLevel(SpriteHandler *);


#define PLAYER_ANIM_INDEX 0

////////////////////////////////////////////////////////////
// Function: playerInit
// ...
////////////////////////////////////////////////////////////
void playerInit()
{
    // create new sprite handler for player
	SpriteHandler * player = newSpriteHandler();
	
	player->characterType = CHAR_PLAYER;
	
	// TODO: Set Animations
	
	// Set userdata
	PlayerData * player_data = malloc( sizeof( PlayerData ) );	
	memset( player_data, 0, sizeof( PlayerData ) ); // Zero set the userdata
	
	player->userData = player_data; // Set the user data
    
	player->_imageIndex = FRAME_PLAYER_1;
	player->size = SD_16x16;
	player->angle = 90;
	player->width = 16;
	player->height = 16;
	
	player_data->map_position_x = CENTER_X + 8;
	player_data->map_position_y = CENTER_Y + 8;
	
	player_data->dir_x = 1;
	player_data->dir_y = 0;
	player_data->millisecondsSinceLastDirectionChange = WAIT_BEFORE_DIRECTION_CHANGE + 1;
	
	player_data->milliseconds_since_last_move_x = 6;
	player_data->milliseconds_since_last_move_y = 6;
	
	player_data->wait_x = 5;
	player_data->wait_y = 999;
	
	//player_data->laser_interval = 200;
	
	player_data->level = 1;
	
	player_data->current_velocity = 0;
	player_data->total_velocity = 0;
	player_data->max_velocity = 20;
	
    player_data->is_moving_two_pixels_at_a_time = FALSE;
    player_data->warp_drive_is_engaged = FALSE;
	
	player_data->acceleration = WAIT_BEFORE_ONE_PIXEL_ACCELERATION;
	player_data->deceleration = WAIT_BEFORE_STANDARD_DECELERATION;
	
	player_data->milliseconds_since_last_velocity_increase = 0;
	player_data->milliseconds_since_last_velocity_decrease = 0;
	
	g_player_map_pos_x = player_data->map_position_x;
	g_player_map_pos_y = player_data->map_position_y;
	
	player_data->level_one_enemies_killed = 0;
    player_data->level_two_enemies_killed = 0;
    player_data->level_three_enemies_killed = 0;
	player_data->total_enemies_killed = 0;
	
	player_data->level = 0;
	player_data->total_velocity_cap = 10;
	player_data->has_earned_warp_drive = FALSE;
	
	// Set up stupid animations shit
	AnimationInfo anim;
	
	anim.updatesPerFrame = 20;
	anim.numFrames = 2;
	anim.looping = TRUE;
	anim.flip = FLIP_NONE;
	anim.frames[ 0 ] = FRAME_PLAYER_1;
	anim.frames[ 1 ] = FRAME_PLAYER_2;
	
	AddSpriteAnimation( player, PLAYER_ANIM_INDEX, anim );
	
	SetSpriteAnimation( player, PLAYER_ANIM_INDEX );	// set animation
	
	updateSpriteAttributes( player );
}


////////////////////////////////////////////////////////////
// Function: playerUpdateFunction
// ...
////////////////////////////////////////////////////////////
void playerUpdateFunction( SpriteHandler * sprite_handler )
{
	if ( sprite_handler->userData == NULL ) {
		return;
	}
	
	// Update animation
	UpdateSpriteAnimation( sprite_handler );
	
	PlayerData * player_data = sprite_handler->userData;
    
    ManagePlayerLevel(sprite_handler);
	
	if (buttonIsDown(BUTTON_B)) {
	    player_data->deceleration = WAIT_BEFORE_BRAKED_DECELERATION;
    }
    else {
        player_data->deceleration = WAIT_BEFORE_STANDARD_DECELERATION;
    }
	
	if (buttonIsDown(BUTTON_LEFT)) {
	    RotatePlayerCounterClockwise(sprite_handler);
    }
    else if (buttonIsDown(BUTTON_RIGHT)) {
        RotatePlayerClockwise(sprite_handler);
    }
    
    //UpdateFireLaser(sprite_handler);
    
    SetDirX(sprite_handler);
    SetDirY(sprite_handler);
    
    player_data->wait_x = GetTimeToWaitBeforeMoveX(player_data->max_velocity - player_data->current_velocity, sprite_handler->angle);
    player_data->wait_y = GetTimeToWaitBeforeMoveY(player_data->max_velocity  - player_data->current_velocity, sprite_handler->angle);
	
	if (buttonIsDown(BUTTON_A) && player_data->total_velocity < player_data->total_velocity_cap) {
	   IncreasePlayerVelocity(sprite_handler);
	}
	else {
	    DecreasePlayerVelocity(sprite_handler);
    }
    
    // can only use warp drive if player kills every enemy except the boss
    if (player_data->has_earned_warp_drive) {
        ManageWarpDrive(sprite_handler);
    }
	
	if (player_data->current_velocity > 0) {
        UpdatePlayerShipPosition(sprite_handler);
    }
	
	// update player timers
	player_data->millisecondsSinceLastDirectionChange += getElapsedTime();
	player_data->milliseconds_since_last_move_x += getElapsedTime();
    player_data->milliseconds_since_last_move_y += getElapsedTime();
    //player_data->millisecondsSinceLastLaser += getElapsedTime();
	
	updateSpriteAttributes( sprite_handler );
	
	g_player_map_pos_x = player_data->map_position_x;
	g_player_map_pos_y = player_data->map_position_y;
}


////////////////////////////////////////////////////////////
// Function: ManagePlayerLevel
// ...
////////////////////////////////////////////////////////////
void ManagePlayerLevel(SpriteHandler * player)
{
    PlayerData * player_data = player->userData;
    
	if (player_data->level_one_enemies_killed >= 5 && player_data->level == 0) {
	    player_data->level = player_data->level + 1;
	    PlaySound( &s_upgrade );
    }
    if (player_data->level_two_enemies_killed >= 5 && player_data->level == 1) {
	   player_data->level = player_data->level + 1;
	   PlaySound( &s_upgrade );
    }
    if (player_data->level_three_enemies_killed >= 5 && player_data->level == 2) {
	   player_data->level = player_data->level + 1;
	   PlaySound( &s_upgrade );
    }
    
    switch (player_data->level) {
        case 0:
            player_data->total_velocity_cap = 15;
            break;
        case 1:
            player_data->total_velocity_cap = 20;
            break;
        case 2:
            player_data->total_velocity_cap = 23;
            break;
        case 3:
            player_data->total_velocity_cap = 30;
            break;
    }
    
    if (player_data->total_enemies_killed >= 21 && !player_data->has_earned_warp_drive) {
        player_data->has_earned_warp_drive = TRUE;
        PlaySound( &s_upgrade );
    }
}


////////////////////////////////////////////////////////////
// Function: ManageWarpDrive
// ...
////////////////////////////////////////////////////////////
void ManageWarpDrive(SpriteHandler * player)
{
    PlayerData * player_data = player->userData;
    
    if (buttonIsDown(BUTTON_UP)) {
        if (player_data->is_moving_two_pixels_at_a_time &&
            player_data->current_velocity == player_data->max_velocity - TWO_PIXEL_VELOCITY_BUFFER &&
            !player_data->warp_drive_is_engaged)
        {
            PlaySound( &s_engine );
            player_data->warp_drive_is_engaged = TRUE;
            player_data->current_velocity = player_data->max_velocity - 2;
        }
    }
    else {
        if (player_data->warp_drive_is_engaged) {
            player_data->warp_drive_is_engaged = FALSE;
            player_data->current_velocity = player_data->max_velocity - TWO_PIXEL_VELOCITY_BUFFER;
        }
    }
}


////////////////////////////////////////////////////////////
// Function: UpdatePlayerVelocity
// ...
////////////////////////////////////////////////////////////
void IncreasePlayerVelocity(SpriteHandler * player)
{
    PlayerData * player_data = player->userData;
    
    player_data->milliseconds_since_last_velocity_increase += getElapsedTime();
    player_data->milliseconds_since_last_velocity_decrease = 0;
    
    // increase velocity
	if (player_data->milliseconds_since_last_velocity_increase > player_data->acceleration) {
        player_data->milliseconds_since_last_velocity_increase = 0;

        if (player_data->is_moving_two_pixels_at_a_time) {
           if (player_data->current_velocity < player_data->max_velocity - TWO_PIXEL_VELOCITY_BUFFER) {
	           player_data->current_velocity = player_data->current_velocity + 1;
	           player_data->total_velocity = player_data->total_velocity + 1;
            }
        }
        else {
	       if (player_data->current_velocity < player_data->max_velocity) {
	           player_data->current_velocity = player_data->current_velocity + 1;
	           player_data->total_velocity = player_data->total_velocity + 1;
	
	           if (player_data->current_velocity == player_data->max_velocity) {
	               player_data->is_moving_two_pixels_at_a_time = TRUE;
	               player_data->current_velocity = INITIAL_VELOCITY_FOR_TWO_PIXEL_AT_A_TIME_MOVEMENT;
	               player_data->acceleration = WAIT_BEFORE_TWO_PIXEL_ACCELERATION;
               }
            }
        }
    }
}


////////////////////////////////////////////////////////////
// Function: UpdatePlayerVelocity
// ...
////////////////////////////////////////////////////////////
void DecreasePlayerVelocity(SpriteHandler * player)
{
    PlayerData * player_data = player->userData;
    
    player_data->milliseconds_since_last_velocity_decrease += getElapsedTime();
    player_data->milliseconds_since_last_velocity_increase = 0;

    // decrease velocity
	if (player_data->milliseconds_since_last_velocity_decrease > player_data->deceleration) {
        player_data->milliseconds_since_last_velocity_decrease = 0;

        if (player_data->current_velocity > 0) {
           player_data->current_velocity = player_data->current_velocity - 1;
           player_data->total_velocity = player_data->total_velocity - 1;
        }

        if (player_data->is_moving_two_pixels_at_a_time && player_data->current_velocity == INITIAL_VELOCITY_FOR_TWO_PIXEL_AT_A_TIME_MOVEMENT) {
            player_data->is_moving_two_pixels_at_a_time = FALSE;
            player_data->current_velocity = player_data->max_velocity - 2;
            player_data->acceleration = WAIT_BEFORE_ONE_PIXEL_ACCELERATION;
        }


    }
}


////////////////////////////////////////////////////////////
// Function: UpdatePlayerShipAngle
// ...
////////////////////////////////////////////////////////////
void RotatePlayerCounterClockwise(SpriteHandler * player)
{
    PlayerData * player_data = player->userData;

    if (player_data->millisecondsSinceLastDirectionChange > WAIT_BEFORE_DIRECTION_CHANGE) {
        player_data->millisecondsSinceLastDirectionChange = 0;

        if (player->angle == 0) {
            player->angle = 330;
        }
        else {
            player->angle -= 30;
        }
    }
}


////////////////////////////////////////////////////////////
// Function: UpdatePlayerShipAngle
// ...
////////////////////////////////////////////////////////////
void RotatePlayerClockwise(SpriteHandler * player)
{
    PlayerData * player_data = player->userData;

    if (player_data->millisecondsSinceLastDirectionChange > WAIT_BEFORE_DIRECTION_CHANGE) {
        player_data->millisecondsSinceLastDirectionChange = 0;
        player->angle += 30;

        if (player->angle > 359) {
            player->angle = 0;
        }
    }
}


////////////////////////////////////////////////////////////
// Function: UpdatePlayerShipPosition
// ...
////////////////////////////////////////////////////////////
void UpdatePlayerShipPosition(SpriteHandler * player)
{
    int new_x = 0;
    int new_y = 0;
    int number_to_move_bg = 0;
    
	PlayerData * player_data = player->userData;
	
	if (player_data->milliseconds_since_last_move_x > player_data->wait_x) {
        player_data->milliseconds_since_last_move_x = 0;
        
        if (player_data->is_moving_two_pixels_at_a_time) {
            new_x = player_data->map_position_x + (2 * player_data->dir_x);
            number_to_move_bg = 2;
        }
        else {
            new_x = player_data->map_position_x + player_data->dir_x;
            number_to_move_bg = 1;
        }
    }
    
   	if (player_data->milliseconds_since_last_move_y > player_data->wait_y) {
        player_data->milliseconds_since_last_move_y = 0;

        if (player_data->is_moving_two_pixels_at_a_time) {
            new_y = player_data->map_position_y + (2 * player_data->dir_y);
            number_to_move_bg = 2;
        }
        else {
            new_y = player_data->map_position_y + player_data->dir_y;
            number_to_move_bg = 1;
        }
    }
    
    if (new_x > 8 && new_x < STAR_MAP_WIDTH_PIXELS - 8 - player->width) {
        player_data->map_position_x = new_x;
        
        if (player_data->dir_x < 0) {
            if ( BgCanScrollLeft && player_data->map_position_x < STAR_MAP_WIDTH_PIXELS - CAMERA_BORDER_BUFFER_WIDTH ) {
                DecrementBgX(number_to_move_bg);
            }
        }
        else if (player_data->dir_x > 0) {
            if ( BgCanScrollRight && player_data->map_position_x > CAMERA_BORDER_BUFFER_WIDTH ) {
                IncrementBgX(number_to_move_bg);
            }
        }
    }
    
    if (new_y > 8 && new_y < STAR_MAP_WIDTH_PIXELS - 8 - player->height) {
        player_data->map_position_y = new_y;
        
        if (player_data->dir_y < 0) {
            if ( BgCanScrollUp && player_data->map_position_y < STAR_MAP_HEIGHT_PIXELS - CAMERA_BORDER_BUFFER_HEIGHT ) {
                DecrementBgY(number_to_move_bg);
            }
        }
        else if (player_data->dir_y > 0) {
            if ( BgCanScrollDown && player_data->map_position_y > CAMERA_BORDER_BUFFER_HEIGHT ) {
                IncrementBgY(number_to_move_bg);
            }
        }
    }
	
	/*
    if (player_data->milliseconds_since_last_move_x > player_data->wait_x) {
        player_data->milliseconds_since_last_move_x = 0;

        // player is moving left
        if (player_data->dir_x < 0)
		{
			// Check if player is right of left boundary
			if ( player_data->map_position_x > 8 )
			{
				player_data->map_position_x = (player_data->map_position_x - 1);	// move player to the left
			
				// decrement the background position
				if ( BgCanScrollLeft && player_data->map_position_x < STAR_MAP_WIDTH_PIXELS - CAMERA_BORDER_BUFFER_WIDTH )
				{
					DecrementBgX(1);
				}
			}
        }

        // player is moving right
        if (player_data->dir_x > 0)
		{
			// Check if player is left of right boundary
			if ( player_data->map_position_x < STAR_MAP_WIDTH_PIXELS - 8 - player->width )
			{
				player_data->map_position_x = (player_data->map_position_x + 1);	// move player to the left
			
				// decrement the background position
				if ( BgCanScrollRight && player_data->map_position_x > CAMERA_BORDER_BUFFER_WIDTH )
				{
					IncrementBgX(1);
				}
			}
        }
    }

    if (player_data->milliseconds_since_last_move_y > player_data->wait_y) {
        player_data->milliseconds_since_last_move_y = 0;

        // player is moving up
        if (player_data->dir_y < 0)
		{
			// Check if player is right of left boundary
			if ( player_data->map_position_y > 8 )
			{
				player_data->map_position_y = (player_data->map_position_y - 1);	// move player to the left
			
				// decrement the background position
				if ( BgCanScrollUp && player_data->map_position_y < STAR_MAP_HEIGHT_PIXELS - CAMERA_BORDER_BUFFER_HEIGHT )
				{
					DecrementBgY(1);
				}
			}
        }

        // player is moving down
        if (player_data->dir_y > 0)
		{
			// Check if player is right of left boundary
			if ( player_data->map_position_y < STAR_MAP_WIDTH_PIXELS - 8 - player->height )
			{
				player_data->map_position_y = (player_data->map_position_y + 1);	// move player to the left
			
				// decrement the background position
				if ( BgCanScrollDown && player_data->map_position_y > CAMERA_BORDER_BUFFER_HEIGHT )
				{
					IncrementBgY(1);
				}
			}
        }
    }
    */
}


////////////////////////////////////////////////////////////
// Function: UpdateFireLaser
// ...
////////////////////////////////////////////////////////////
/*
void UpdateFireLaser(SpriteHandler * player)
{
	PlayerData * player_data = player->userData;
    
    if (player_data->millisecondsSinceLastLaser > player_data->laser_interval && buttonIsDown(BUTTON_B)) {
        player_data->millisecondsSinceLastLaser = 0;
        laserInit(player, player_data->wait_x, player_data->wait_y, CHAR_PLAYER, player_data->level);
    }
}
*/


////////////////////////////////////////////////////////////
// Function: playerInit
// ...
////////////////////////////////////////////////////////////
void SetDirX(SpriteHandler * sprite_handler)
{
    int angle = sprite_handler->angle;
    int dir_x_to_set = 0;

    if (angle == 0 || angle == 180) {
        dir_x_to_set = 0;
    }
    else if (angle > 0 && angle < 180) {
        dir_x_to_set = 1;
    }
    else if (angle > 180 && angle < 360) {
        dir_x_to_set = -1;
    }
    
    PlayerData * player_data = sprite_handler->userData;
    player_data->dir_x = dir_x_to_set;
}


////////////////////////////////////////////////////////////
// Function: playerInit
// ...
////////////////////////////////////////////////////////////
void SetDirY(SpriteHandler * sprite_handler)
{
    int angle = sprite_handler->angle;
    int dir_y_to_set = 0;

    if (angle == 90 || angle == 270) {
        dir_y_to_set = 0;
    }
    else if ((angle > 270 && angle < 360) || (angle > 0 && angle < 90) || angle == 0) {
        dir_y_to_set = -1;
    }
    else if (angle > 90 && angle < 270) {
        dir_y_to_set = 1;
    }
    
    PlayerData * player_data = sprite_handler->userData;
    player_data->dir_y = dir_y_to_set;
}


////////////////////////////////////////////////////////////
// Function: GetPlayerMapPosX
// ...
////////////////////////////////////////////////////////////
int GetPlayerMapPosX()
{
    return g_player_map_pos_x;
}


////////////////////////////////////////////////////////////
// Function: GetPlayerMapPosX
// ...
////////////////////////////////////////////////////////////
int GetPlayerMapPosY()
{
    return g_player_map_pos_y;
}


void playerHandleCollision( SpriteHandler * a, SpriteHandler * b )
{
    BOOL did_lose = FALSE;
    BOOL did_win = FALSE;
    
	if ( b->characterType ==  CHAR_ENEMY)
	{
	    EnemyData * enemy_data = b->userData;
	    PlayerData * player_data = a->userData;
	    
	    switch (enemy_data->level) {
            case 1:
                player_data->level_one_enemies_killed = player_data->level_one_enemies_killed + 1;
                player_data->total_enemies_killed = player_data->total_enemies_killed + 1;

                if (player_data->total_velocity < 8) {
                    did_lose = TRUE;
                }

                break;
            case 2:
                player_data->level_two_enemies_killed = player_data->level_two_enemies_killed + 1;
                player_data->total_enemies_killed = player_data->total_enemies_killed + 1;

                if (player_data->total_velocity < 18) {
                    did_lose = TRUE;
                }
            
               break;
            case 3:
                player_data->level_three_enemies_killed = player_data->level_three_enemies_killed + 1;
                player_data->total_enemies_killed = player_data->total_enemies_killed + 1;

                if (player_data->total_velocity < 21) {
                    did_lose = TRUE;
                }

                break;
            case 4:
                if (player_data->warp_drive_is_engaged) {
                    did_win = TRUE;
                }
                else {
                    did_lose = TRUE;
                }

                break;
        }

        if (did_win) 
		{
		    b->alive = FALSE;
            replaceWithExplosion( b );
            updateSpriteAttributes( b );
            
            //PlaySound( &s_win );
			SetVictoryState();
        }
        else if (did_lose) {
            a->alive = FALSE;
            replaceWithExplosion( a );
            updateSpriteAttributes( a );
            
			SetDeathState();
        }
        else {
            b->alive = FALSE;
            replaceWithExplosion( b );
            updateSpriteAttributes( b );
            addNextEnemy();
        }
	}
}

#endif
